#include "awi_dfu_unpack.h"
void awi_dfu_unpack_init(awi_dfu_unpack_t *unpack)
{
	unpack->state = AWI_DFU_UNPACK_MAGIC_FIRST;
	unpack->cache_header[0] = 0xFF;
	unpack->cache_header[1] = 0xA5;
	unpack->payload_extra = NULL;
	unpack->payload_extra_length = 0;
}

void awi_dfu_unpack_clean(awi_dfu_unpack_t *unpack)
{
	if (unpack->payload_extra != NULL) {
		awi_free(unpack->payload_extra); 
	}
}

void awi_dfu_unpack_reset(awi_dfu_unpack_t *unpack)
{
	unpack->state = AWI_DFU_UNPACK_MAGIC_FIRST;
	unpack->cache_header[0] = 0xFF;
	unpack->cache_header[1] = 0xA5;
}
	
int awi_dfu_unpack_feed(awi_dfu_unpack_t *unpack, const uint8_t *buffer, int len, int *consumed)
{
	int pos = 0;
	int ret = 0;
	
	while(pos < len) {
		switch(unpack->state) {
			case AWI_DFU_UNPACK_MAGIC_FIRST:
				if(buffer[pos] == 0xFF) {
					unpack->state = AWI_DFU_UNPACK_MAGIC_SECOND;
				}
				++pos;
				break;
			case AWI_DFU_UNPACK_MAGIC_SECOND:
				if(buffer[pos] == 0xA5) {
					unpack->state = AWI_DFU_UNPACK_HEADER;
					unpack->header_pos = 2;
					++pos;
				} else {
					unpack->state = AWI_DFU_UNPACK_MAGIC_FIRST;
				}
				break; 
			case AWI_DFU_UNPACK_HEADER: 
				unpack->cache_header[unpack->header_pos] = buffer[pos];
				++pos;
				++unpack->header_pos;
				if(unpack->header_pos == AWI_DFU_PROTOCOL_HEADER_LENGTH) {
					struct _dfu_header *header = (struct _dfu_header*)unpack->cache_header;
					
					unpack->header.version = awi_ntohs(header->version);
					unpack->header.dst = awi_ntohs(header->dst);
					unpack->header.src = awi_ntohs(header->src);
					unpack->header.ctrl = awi_ntohs(header->ctrl);
					unpack->header.len = awi_ntohs(header->len);
					unpack->header.crc = awi_htons(header->crc);
					header->crc = 0;
					unpack->compute_crc = crc_xmodem((const unsigned char*)unpack->cache_header, AWI_DFU_PROTOCOL_HEADER_LENGTH);
					if(unpack->header.len > AWI_DFU_UNPACK_DEF_BUFFER_SIZE) {
						if(unpack->header.len > unpack->payload_extra_length && unpack->payload_extra != NULL) {
							awi_free(unpack->payload_extra);
							unpack->payload_extra = NULL;
							unpack->payload_extra_length = 0;
						}
						if(unpack->payload_extra == NULL) {
							unpack->payload_extra_length = awi_align((unpack->header.len), 16);
							unpack->payload_extra = (uint8_t*)awi_malloc(unpack->payload_extra_length);
							if(NULL == unpack->payload_extra) {
								unpack->payload_extra_length = 0;
								ret = -1;
								goto end;
							}
						}
					}
					if(unpack->header.len > 0) {
						unpack->payload_pos = 0;
						unpack->state = AWI_DFU_UNPACK_PAYLOAD;
					} else {
						if(unpack->compute_crc != unpack->header.crc) {
							ret = -1;
							goto end;
						}
						unpack->state = AWI_DFU_UNPACK_DONE;
					}
				}
				break;
			case AWI_DFU_UNPACK_PAYLOAD:
				if(unpack->header.len > AWI_DFU_UNPACK_DEF_BUFFER_SIZE) {
					unpack->payload_extra[unpack->payload_pos] = buffer[pos];
				} else {
					unpack->payload[unpack->payload_pos] = buffer[pos];
				}
				++pos;
				++unpack->payload_pos;
				if(unpack->payload_pos == unpack->header.len) {
					unpack->compute_crc = crc_xmodem_update((const unsigned char *)(unpack->header.len > AWI_DFU_UNPACK_DEF_BUFFER_SIZE ? unpack->payload_extra : unpack->payload),
																									 unpack->payload_pos, unpack->compute_crc);
					if(unpack->compute_crc != unpack->header.crc) {
						ret = -1;
						goto end;
					}
					unpack->state = AWI_DFU_UNPACK_DONE;
				}
				break;
			case AWI_DFU_UNPACK_DONE:
				goto end;
				break;
		}
	}
end:
	*consumed = pos;
	return ret;
}

int awi_dfu_unpack_proc(awi_dfu_unpack_t *unpack, const uint8_t *buffer, int bytes)
{
	if(bytes < AWI_DFU_PROTOCOL_HEADER_LENGTH) {
		aw_print("bytes = %d shorter than header length = %d", bytes, AWI_DFU_PROTOCOL_HEADER_LENGTH);
		return -1;
	}
	struct _dfu_header *header = (struct _dfu_header*)buffer;
	unpack->header.version = awi_ntohs(header->version);
	unpack->header.dst = awi_ntohs(header->dst);
	unpack->header.src = awi_ntohs(header->src);
	unpack->header.ctrl = awi_ntohs(header->ctrl);
	unpack->header.len = awi_ntohs(header->len);
	unpack->header.crc = awi_ntohs(header->crc);
	
	if(bytes != unpack->header.len + AWI_DFU_PROTOCOL_HEADER_LENGTH) {
		aw_print("len not fit len = %d header = %d bytes = %d", unpack->header.len, AWI_DFU_PROTOCOL_HEADER_LENGTH, bytes);
		return -1;
	}
	header->crc = 0;
	unpack->compute_crc = crc_xmodem((const unsigned char*)buffer, bytes);
	aw_print("recved crc = %x compute crc = %x", unpack->header.crc, unpack->compute_crc);
	if(unpack->header.crc != unpack->compute_crc) {
		aw_print("crc compare fail");
		return -1;
	}
	
	if(unpack->header.len > AWI_DFU_UNPACK_DEF_BUFFER_SIZE) {
		if(unpack->header.len > unpack->payload_extra_length && unpack->payload_extra != NULL) {
			awi_free(unpack->payload_extra);
			unpack->payload_extra = NULL;
			unpack->payload_extra_length = 0;
		}
		if(unpack->payload_extra == NULL) {
			unpack->payload_extra_length = awi_align((unpack->header.len), 16);
			unpack->payload_extra = (uint8_t*)awi_malloc(unpack->payload_extra_length);
			if(NULL == unpack->payload_extra) {
				unpack->payload_extra_length = 0;
				return -1;
			}
		}
		memcpy(unpack->payload_extra, buffer + AWI_DFU_PROTOCOL_HEADER_LENGTH, unpack->header.len);
	} else {
		memcpy(unpack->payload, buffer + AWI_DFU_PROTOCOL_HEADER_LENGTH, unpack->header.len);
	}
	unpack->state = AWI_DFU_UNPACK_DONE;
	return 0;
}